---
title: Kontent.ai & Astro
description: Kontent.ai를 CMS로 사용하여 Astro 프로젝트에 콘텐츠 추가
type: cms
service: Kontent.ai
i18nReady: true
---

import { FileTree } from '@astrojs/starlight/components';
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'

[Kontent.ai](https://www.kontent.ai/)는 AI 기능이 지원되는 구조화되고 모듈화된 방식으로 콘텐츠를 관리할 수 있는 헤드리스 CMS입니다.

## Astro와 통합

이 섹션에서는 [Kontent.ai TypeScript SDK](https://github.com/kontent-ai/delivery-sdk-js)를 사용하여 Kontent.ai 프로젝트를 Astro 애플리케이션에 연결합니다.

### 전제조건

시작하려면 다음이 필요합니다.

1. **Kontent.ai 프로젝트** - 아직 Kontent.ai 계정이 없다면 [무료 회원가입](https://app.kontent.ai/sign-up) 후 새 프로젝트를 생성하세요.

2. **Delivery API keys** - 게시된 콘텐츠에는 Environment ID가 필요하고 초안을 가져오려면 Preview API key가 필요합니다 (선택 사항). 두 키 모두 Kontent.ai의 **Environment Settings -> API keys** 탭에 있습니다.

### 자격 증명 설정

Astro에 Kontent.ai 자격 증명을 추가하려면 다음 변수를 사용하여 프로젝트 루트에 `.env` 파일을 만듭니다.

```ini title=".env"
KONTENT_ENVIRONMENT_ID=YOUR_ENVIRONMENT_ID
KONTENT_PREVIEW_API_KEY=YOUR_PREVIEW_API_KEY
```

이제 이러한 환경 변수를 Astro 프로젝트에서 사용할 수 있습니다.

이러한 [환경 변수를 위한 TypeScript IntelliSense](/ko/guides/environment-variables/#typescript-자동-완성)를 얻으려면 `src/` 디렉터리에 새 `env.d.ts` 파일을 만들고 다음과 같이 `ImportMetaEnv`를 구성할 수 있습니다.

```ts title="src/env.d.ts"
interface ImportMetaEnv {
  readonly KONTENT_ENVIRONMENT_ID: string;
  readonly KONTENT_PREVIEW_API_KEY: string;
}
```

이제 루트 디렉터리에 다음과 같은 새 파일이 포함되어야 합니다.

<FileTree title="Project Structure">
- src/
  - **env.d.ts**
- **.env**
- astro.config.mjs
- package.json
</FileTree>

### 종속성 설치

Astro를 Kontent.ai 프로젝트와 연결하려면 [Kontent.ai TypeScript SDK](https://github.com/kontent-ai/delivery-sdk-js)를 설치하세요.

<PackageManagerTabs>
  <Fragment slot="npm">
  ```shell
    npm install @kontent-ai/delivery-sdk
  ```
  </Fragment>
  <Fragment slot="pnpm">
  ```shell
    pnpm add @kontent-ai/delivery-sdk
  ```
  </Fragment>
  <Fragment slot="yarn">
  ```shell
    yarn add @kontent-ai/delivery-sdk
  ```
  </Fragment>
</PackageManagerTabs>

다음으로 Astro 프로젝트의 `src/lib/` 디렉터리에 `kontent.ts`라는 새 파일을 만듭니다.

```ts title="src/lib/kontent.ts"
import { createDeliveryClient } from "@kontent-ai/delivery-sdk";

export const deliveryClient = createDeliveryClient({
    environmentId: import.meta.env.KONTENT_ENVIRONMENT_ID,
    previewApiKey: import.meta.env.KONTENT_PREVIEW_API_KEY,
});
```

:::note
[Astro에서 환경 변수 가져오기](/ko/guides/environment-variables/#환경-변수-가져오기)에 대해 자세히 알아보세요.
:::

이 구현은 `.env` 파일의 자격 증명을 사용하여 새로운 `DeliveryClient` 객체를 생성합니다.

:::note[Previews]
`previewApiKey`는 선택사항입니다. 사용하면 워크플로의 상태에 관계없이 콘텐츠 항목의 최신 버전을 반환하도록 Delivery API 엔드포인트에 대한 [각 쿼리를 구성](https://github.com/kontent-ai/delivery-sdk-js#enable-preview-mode-per-query)할 수 있습니다. 그렇지 않으면 게시된 항목만 반환됩니다.
:::

마지막으로 Astro 프로젝트의 루트 디렉터리에는 이제 다음과 같은 새 파일이 포함되어야 합니다.

<FileTree title="Project Structure">
- src/
  - lib/
    - **kontent.ts**
  - env.d.ts
- .env
- astro.config.mjs
- package.json
</FileTree>

### 데이터 가져오기

이제 `DeliveryClient`를 모든 컴포넌트에서 사용할 수 있습니다. 콘텐츠를 가져오려면 `DeliveryClient`와 메서드 체이닝을 사용하여 원하는 항목을 정의하세요. 이 예시에서는 블로그 게시물의 기본 가져오기를 보여주고 제목을 목록으로 렌더링합니다.

```astro title="src/pages/index.astro" ins={2-7, 16-20}
---
import { deliveryClient } from "../lib/kontent";

const blogPosts = await deliveryClient
    .items()
    .type("blogPost")
    .toPromise()
---
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width" />
		<title>Astro</title>
	</head>
	<body>
        <ul>
        {blogPosts.data.items.map(blogPost => (
            <li>{blogPost.elements.title.value}</li>
        ))}
        </ul>
    </body>
</html>
```

[Kontent.ai 문서](https://kontent.ai/learn/develop/hello-world/get-content/javascript)에서 더 많은 쿼리 옵션을 찾을 수 있습니다.

## Astro와 Kontent.ai로 블로그 만들기

위 설정을 통해 이제 Kontent.ai를 콘텐츠 소스로 사용하는 블로그를 만들 수 있습니다.

### 전제조건

1. **Kontent.ai 프로젝트** - 이 튜토리얼에서는 빈 프로젝트를 사용하는 것이 좋습니다. 콘텐츠 모델에 이미 일부 콘텐츠 타입이 있는 경우 이를 사용할 수 있지만 콘텐츠 모델에 맞게 코드 조각을 수정해야 합니다.

2. **Kontent.ai에서 콘텐츠를 가져오도록 구성된 Astro 프로젝트** - Kontent.ai를 사용하여 Astro 프로젝트를 설정하는 방법에 대한 자세한 내용은 위를 참조하세요.

### 콘텐츠 모델 설정

Kontent.ai에서 **Content model**로 이동하여 다음 필드와 값을 사용하여 새 콘텐츠 타입을 만듭니다.

* **Name:** Blog Post
* Elements:
	* Text field
		* **Name:** Title
		* **Element Required:** yes
	* Rich text field
		* **Name:** Teaser
		* **Element Required:** yes
		* **Allowed in this element:** only check Text
	* Rich text field
		* **Name:** Content
		* **Element Required:** yes
	* Date & time field
		* **Name:** Date
	* URL slug field
		* **Name:** URL slug
		* **Element Required:** yes
		* **Auto-generate from:** select "Title"

그런 다음 **Save Changes**을 클릭하세요.

### 콘텐츠 생성

이제 **Content & assets** 탭으로 이동하여 **Blog Post** 타입의 새 콘텐츠 항목을 만듭니다. 다음 값을 사용하여 필드를 채웁니다.

* **Content item name:** Astro
* **Title:** Astro is amazing
* **Teaser:** Astro is an all-in-one framework for building fast websites faster.
* **Content:** You can use JavaScript to implement the website functionality, but no client bundle is necessary.
* **Date & time:** select today
* **URL slug:** astro-is-amazing

완료되면 상단의 **Publish** 버튼을 사용하여 블로그 게시물을 게시하세요.

*참고: 다음 단계로 넘어가기 전에 원하는 만큼 블로그 게시물을 작성해 보세요.*

### TypeScript에서 콘텐츠 모델 생성

다음으로 콘텐츠 모델에서 TypeScript 타입을 생성합니다.

:::note
이 단계는 선택 사항이지만 훨씬 더 나은 개발자 경험을 제공하고 런타임이 아닌 빌드 시간에 잠재적인 문제를 발견할 수 있습니다.
:::

먼저 [Kontent.ai JS model generator](https://github.com/kontent-ai/model-generator-js), [ts-node](https://github.com/TypeStrong/ts-node), [dotenv](https://github.com/motdotla/dotenv)를 설치합니다.

<PackageManagerTabs>
  <Fragment slot="npm">
  ```shell
    npm install @kontent-ai/model-generator ts-node dotenv
  ```
  </Fragment>
  <Fragment slot="pnpm">
  ```shell
    pnpm add @kontent-ai/model-generator ts-node dotenv
  ```
  </Fragment>
  <Fragment slot="yarn">
  ```shell
    yarn add @kontent-ai/model-generator ts-node dotenv
  ```
  </Fragment>
</PackageManagerTabs>

그런 다음 package.json에 다음 스크립트를 추가합니다.

```json title="package.json"
{
    ...
    "scripts": {
        ...
        "regenerate:models": "ts-node --esm ./generate-models.ts"
    },
}
```

타입에는 공개 API에서 사용할 수 없는 프로젝트에 대한 구조적 정보가 필요하므로 `.env` 파일에 콘텐츠 관리 API key도 추가해야 합니다. **Environment settings -> API keys -> Management API**에서 키를 생성할 수 있습니다.

```ini title=".env" ins={3}
KONTENT_ENVIRONMENT_ID=YOUR_ENVIRONMENT_ID
KONTENT_PREVIEW_API_KEY=YOUR_PREVIEW_API_KEY
KONTENT_MANAGEMENT_API_KEY=YOUR_MANAGEMENT_API_KEY
```

마지막으로, 모델을 생성하도록 모델 생성기를 구성하는 `generate-models.ts` 스크립트를 추가합니다.

```ts title="generate-models.ts"
import { generateModelsAsync, textHelper } from '@kontent-ai/model-generator'
import { rmSync, mkdirSync } from 'fs'

import * as dotenv from 'dotenv'
dotenv.config()

const runAsync = async () => {
	rmSync('./src/models', { force: true, recursive: true })
	mkdirSync('./src/models')

	// 작업 디렉토리를 models로 변경
	process.chdir('./src/models')

	await generateModelsAsync({
		sdkType: 'delivery',
		apiKey: process.env.KONTENT_MANAGEMENT_API_KEY ?? '',
		environmentId: process.env.KONTENT_ENVIRONMENT_ID ?? '',
		addTimestamp: false,
		isEnterpriseSubscription: false,
	})
}

// 스스로 호출되는 비동기 함수
;(async () => {
	await runAsync()
})().catch(err => {
	console.error(err)
	throw err
})
```

이제 실행해 보세요.

<PackageManagerTabs>
  <Fragment slot="npm">
  ```shell
    npm run regenerate:models
  ```
  </Fragment>
  <Fragment slot="pnpm">
  ```shell
    pnpm run regenerate:models
  ```
  </Fragment>
  <Fragment slot="yarn">
  ```shell
    yarn run regenerate:models
  ```
  </Fragment>
</PackageManagerTabs>

### 블로그 게시물 목록 표시하기

이제 일부 콘텐츠를 가져올 준비가 되었습니다. 모든 블로그 게시물 목록을 표시하려는 Astro 페이지로 이동합니다 (예: `src/pages`의 홈페이지 `index.astro`).

Astro 페이지의 프런트매터에 있는 모든 블로그 게시물을 가져옵니다:

```astro title="src/pages/index.astro"
---
import { deliveryClient } from '../lib/kontent';
import type { BlogPost } from '../models';
import { contentTypes } from '../models/project/contentTypes';

const blogPosts = await deliveryClient
    .items<BlogPost>
    .type(contentTypes.blog_post.codename)
    .toPromise()
---
```

모델 생성을 건너뛴 경우 타입이 지정되지 않은 객체와 문자열 리터럴을 사용하여 타입을 정의할 수도 있습니다.

```ts
const blogPosts = await deliveryClient
    .items()
    .type("blogPost")
    .toPromise()
```

fetch 호출은 `data.items`의 모든 블로그 게시물 목록을 포함하는 `response` 객체를 반환합니다. Astro 페이지의 HTML 섹션에서 `map()` 함수를 사용하여 블로그 게시물을 나열할 수 있습니다.

```astro title="src/pages/index.astro" ins={11-29}
---
import { deliveryClient } from '../lib/kontent';
import type { BlogPost } from '../models';
import { contentTypes } from '../models/project/contentTypes';

const blogPosts = await deliveryClient
    .items<BlogPost>
    .type(contentTypes.blogPost.codename)
    .toPromise()
---
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>Astro</title>
    </head>
    <body>
        <h1>Blog posts</h1>
        <ul>
            {blogPosts.data.items.map(blogPost => (
                <li>
                    <a href={`/blog/${blogPost.elements.url_slug.value}/`} title={blogPost.elements.title.value}>
                        {blogPost.elements.title.value}
                    </a>
                </li>
            ))}
        </ul>
    </body>
</html>
```

### 개별 블로그 게시물 생성

튜토리얼의 마지막 단계는 자세한 블로그 게시물 페이지를 생성하는 것입니다.

#### 정적 사이트 생성

이 섹션에서는 Astro와 함께 [정적 (SSG) 모드](/ko/guides/routing/#정적ssg-모드)를 사용합니다.

먼저, `/src/pages/blog/`에 `[slug].astro` 파일을 생성합니다. 이 파일은 CMS로부터 모든 데이터를 가져오는 `getStaticPaths` 함수를 내보내야 합니다.

```astro title="src/pages/blog/[slug].astro"
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';

export async function getStaticPaths() {
    const blogPosts = await deliveryClient
        .items<BlogPost>()
        .type(contentTypes.blog_post.codename)
        .toPromise()
---
```

지금까지 이 함수는 Kontent.ai에서 모든 블로그 게시물을 가져왔습니다. 코드 조각은 홈 페이지에서 사용한 것과 정확히 동일합니다.

다음으로 함수는 각 블로그 게시물의 경로와 데이터를 내보내야 합니다. 파일 이름을 `[slug].astro`로 지정했으므로 URL 슬러그를 나타내는 매개변수를 `slug`라고 합니다.

```astro title="src/pages/blog/[slug].astro" ins={12-15}
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';

export async function getStaticPaths() {
    const blogPosts = await deliveryClient
        .items<BlogPost>()
        .type(contentTypes.blog_post.codename)
        .toPromise()

    return blogPosts.data.items.map(blogPost => ({
        params: { slug: blogPost.elements.url_slug.value },
        props: { blogPost }
    }))
}
---
```

마지막 부분은 HTML 템플릿을 제공하고 각 블로그 게시물을 표시하는 것입니다.

```astro title="src/pages/blog/[slug].astro" ins={18-33}
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';

export async function getStaticPaths() {
    const blogPosts = await deliveryClient
        .items<BlogPost>()
        .type(contentTypes.blog_post.codename)
        .toPromise()

    return blogPosts.data.items.map(blogPost => ({
        params: { slug: blogPost.elements.url_slug.value },
        props: { blogPost }
    }))
}

const blogPost: BlogPost = Astro.props.blogPost
---
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>{blogPost.elements.title.value}</title>
    </head>
    <body>
        <article>
            <h1>{blogPost.elements.title.value}</h1>
            <Fragment set:html={blogPost.elements.teaser.value} />
            <Fragment set:html={blogPost.elements.content.value} />
            <time>{new Date(blogPost.elements.date.value ?? "")}</time>
    </body>
</html>
```

Astro 미리보기 (기본적으로 http://localhost:4321/blog/astro-is-amazing/)로 이동하여 렌더링된 블로그 게시물을 확인하세요.

#### 서버 측 렌더링

SSR 모드를 선택한 경우 동적 경로를 사용하여 Kontent.ai에서 페이지 데이터를 가져옵니다.

`/src/pages/blog/`에 `[slug].astro`라는 새 파일을 만들고 다음 코드를 추가하세요. 데이터 가져오기는 이전 사용 사례와 매우 유사하지만 사용된 URL을 기반으로 올바른 블로그 게시물을 찾을 수 있는 `equalsFilter`를 추가합니다.

```astro title="src/pages/blog/[slug].astro"
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';

const { slug } = Astro.params
let blogPost: BlogPost;
try {
    const data = await deliveryClient
        .items<BlogPost>()
        .equalsFilter(contentTypes.blog_post.elements.url_slug.codename, slug ?? '')
        .type(contentTypes.blog_post.codename)
        .limitParameter(1)
        .toPromise()
    blogPost = data.data.items[0]
} catch (error) {
    return Astro.redirect('/404')
}
---
```

생성된 타입을 사용하지 않는 경우 대신 문자열 리터럴을 사용하여 콘텐츠 항목 타입과 필터링된 요소 코드명을 정의할 수 있습니다.

```ts
const data = await deliveryClient
        .items()
        .equalsFilter("url_slug", slug ?? '')
        .type("blog_post")
        .limitParameter(1)
        .toPromise()
```

마지막으로 HTML 코드를 추가하여 블로그 게시물을 렌더링합니다. 이 부분은 정적 생성과 동일합니다.

```astro title="src/pages/blog/[slug].astro" ins={20-33}
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';

const { slug } = Astro.params
let blogPost: BlogPost;
try {
    const data = await deliveryClient
        .items<BlogPost>()
        .equalsFilter(contentTypes.blog_post.elements.url_slug.codename, slug ?? '')
        .type(contentTypes.blog_post.codename)
        .limitParameter(1)
        .toPromise()
    blogPost = data.data.items[0]
} catch (error) {
    return Astro.redirect('/404')
}
---
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>{blogPost.elements.title.value}</title>
    </head>
    <body>
        <article>
            <h1>{blogPost.elements.title.value}</h1>
            <Fragment set:html={blogPost.elements.teaser.value} />
            <Fragment set:html={blogPost.elements.content.value} />
            <time>{new Date(blogPost.elements.date.value ?? '')}</time>
    </body>
</html>
```

### 사이트 게시

웹사이트를 배포하려면 [배포 가이드](/ko/guides/deploy/)를 방문하여 선호하는 호스팅 제공업체의 지침을 따르세요.

#### Kontent.ai 변경에 따른 재빌드

프로젝트가 Astro의 기본 정적 모드를 사용하는 경우 콘텐츠가 변경될 때 새 빌드를 트리거하도록 웹후크를 설정해야 합니다. Netlify 또는 Vercel을 호스팅 공급자로 사용하는 경우 해당 웹후크 기능을 사용하여 Kontent.ai 이벤트에서 새 빌드를 트리거할 수 있습니다.

##### Netlify

Netlify에서 웹후크를 설정하려면:

1. 사이트 대시보드로 이동하여  **Build & deploy**를 클릭합니다.
2. **Continuous Deployment** 탭에서 **Build hooks** 섹션을 찾아 **Add build hook**를 클릭합니다.
3. 웹후크의 이름을 제공하고 빌드를 트리거할 브랜치를 선택합니다. **Save**을 클릭하고 생성된 URL을 복사하세요.

##### Vercel

Vercel에서 웹후크를 설정하려면 다음 안내를 따르세요.

1. 프로젝트 대시보드로 이동하여 **Settings**을 클릭합니다.
2. **Git** 탭에서 **Deploy Hooks** 섹션을 찾습니다.
3. 빌드를 트리거할 웹후크와 브랜치의 이름을 제공합니다. **Add**를 클릭하고 생성된 URL을 복사합니다.

Kontent.ai에 웹후크 추가

[Kontent.ai 앱](https://kontent.ai/learn/docs/webhooks/javascript)에서 **Environment settings -> Webhooks**로 이동합니다. **Create new webhook**를 클릭하고 새 웹후크의 이름을 입력하세요. Netlify 또는 Vercel에서 복사한 URL을 붙여넣고 웹후크를 트리거할 이벤트를 선택하세요. 기본적으로 게시된 콘텐츠가 변경될 때 사이트를 다시 빌드하려면 **Delivery API triggers** 아래에 **Publish** 및 **Unpublish** 이벤트만 필요합니다. 완료되면 저장을 클릭하세요.

이제 Kontent.ai에 새 블로그 게시물을 게시할 때마다 새 빌드가 실행되고 블로그가 업데이트됩니다.
